using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Debugger.Interop;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Engine
{
    // AD7Engine is the primary entrypoint object for the sample engine. 
    //
    // It implements:
    //
    // IDebugEngine2: This interface represents a debug engine (DE). It is used to manage various aspects of a debugging session, 
    // from creating breakpoints to setting and clearing exceptions.
    //
    // IDebugEngineLaunch2: Used by a debug engine (DE) to launch and terminate programs.
    [ComVisible( true )]
    public abstract class AD7Engine : IDebugEngine2, IDebugEngineLaunch2
    {
        #region Protected Properties

        protected Events.Postman Postman { get; set; }
        protected IProgram DebuggedProgram { get; set; }
        protected Breakpoint.Manager Breakpoints { get; private set; } = new Breakpoint.Manager();

        #endregion Protected Properties

        #region C-Tor

        public AD7Engine()
        {
        }

        ~AD7Engine()
        {
        }

        #endregion C-Tor

        #region IDebugEngine2 Members

        public abstract int Attach( IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpProgramNodes, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason );

        // Requests that all programs being debugged by this DE stop execution the next time one of their threads attempts to run.
        // This is normally called in response to the user clicking on the pause button in the debugger.
        // When the break is complete, an AsyncBreakComplete event will be sent back to the debugger.
        int IDebugEngine2.CauseBreak()
        {
            return Constants.S_OK;
        }

        // Called by the SDM to indicate that a synchronous debug event, previously sent by the DE to the SDM,
        // was received and processed. The only event the sample engine sends in this fashion is Program Destroy.
        // It responds to that event by shutting down the engine.
        int IDebugEngine2.ContinueFromSynchronousEvent( IDebugEvent2 eventObject )
        {
            return Constants.S_OK;
        }

        // Creates a pending breakpoint in the engine. A pending breakpoint contains all the information needed to bind a breakpoint to 
        // a location in the debuggee.
        int IDebugEngine2.CreatePendingBreakpoint( IDebugBreakpointRequest2 pBPRequest, out IDebugPendingBreakpoint2 ppPendingBP )
        {
            ppPendingBP = Breakpoints.Create( pBPRequest );

            return Constants.S_OK;
        }

        // Informs a DE that the program specified has been atypically terminated and that the DE should 
        // clean up all references to the program and send a program destroy event.
        int IDebugEngine2.DestroyProgram( IDebugProgram2 pProgram )
        {
            Breakpoints.DebuggedProgram = null;
            DebuggedProgram = null;

            // Tell the SDM that the engine knows that the program is exiting, and that the
            // engine will send a program destroy. We do this because the Win32 debug api will always
            // tell us that the process exited, and otherwise we have a race condition.

            return ( AD7_HRESULT.E_PROGRAM_DESTROY_PENDING );
        }

        // Gets the GUID of the DE.
        int IDebugEngine2.GetEngineId( out Guid guidEngine )
        {
            guidEngine = Defines.Id.Guid.Engine;
            return Constants.S_OK;
        }

        // Removes the list of exceptions the IDE has set for a particular run-time architecture or language.
        // The sample engine does not support exceptions in the debuggee so this method is not actually implemented.
        int IDebugEngine2.RemoveAllSetExceptions( ref Guid guidType )
        {
            return Constants.S_OK;
        }

        // Removes the specified exception so it is no longer handled by the debug engine.
        // The sample engine does not support exceptions in the debuggee so this method is not actually implemented.       
        int IDebugEngine2.RemoveSetException( EXCEPTION_INFO[] pException )
        {
            // The sample engine will always stop on all exceptions.

            return Constants.S_OK;
        }

        // Specifies how the DE should handle a given exception.
        // The sample engine does not support exceptions in the debuggee so this method is not actually implemented.
        int IDebugEngine2.SetException( EXCEPTION_INFO[] pException )
        {
            return Constants.S_OK;
        }

        // Sets the locale of the DE.
        // This method is called by the session debug manager (SDM) to propagate the locale settings of the IDE so that
        // strings returned by the DE are properly localized. The sample engine is not localized so this is not implemented.
        int IDebugEngine2.SetLocale( ushort wLangID )
        {
            return Constants.S_OK;
        }

        // A metric is a registry value used to change a debug engine's behavior or to advertise supported functionality. 
        // This method can forward the call to the appropriate form of the Debugging SDK Helpers function, SetMetric.
        int IDebugEngine2.SetMetric( string pszMetric, object varValue )
        {
            // The sample engine does not need to understand any metric settings.
            return Constants.S_OK;
        }

        // Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored
        // This allows the debugger to tell the engine where that location is.
        int IDebugEngine2.SetRegistryRoot( string pszRegistryRoot )
        {
            // The sample engine does not read settings from the registry.
            return Constants.S_OK;
        }

        #endregion

        #region IDebugEngineLaunch2 Members

        // Determines if a process can be terminated.
        int IDebugEngineLaunch2.CanTerminateProcess( IDebugProcess2 process )
        {
            return Constants.S_OK;
        }

        // Launches a process by means of the debug engine.
        // Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger 
        // to the suspended program. However, there are circumstances in which the debug engine may need to launch a program 
        // (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language), 
        // in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method
        // The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state.
        int IDebugEngineLaunch2.LaunchSuspended( string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process )
        {
            Postman = new Events.Postman( this, ad7Callback );


            ProcessStartInfo info = new ProcessStartInfo();

            info.FileName = exe;
            info.Arguments = args;

            Process launcherProcess = Process.Start( info );

            AD_PROCESS_ID id = new AD_PROCESS_ID();
            id.dwProcessId = (uint)launcherProcess.Id;
            port.GetProcess( id, out process );
            return Constants.S_OK;
        }

        // Resume a process launched by IDebugEngineLaunch2.LaunchSuspended
        public abstract int ResumeProcess( IDebugProcess2 process );

        // This function is used to terminate a process that the SampleEngine launched
        // The debugger will call IDebugEngineLaunch2::CanTerminateProcess before calling this method.
        int IDebugEngineLaunch2.TerminateProcess( IDebugProcess2 process )
        {
            AD_PROCESS_ID[] pProcessId = new AD_PROCESS_ID[ 1 ];
            if( process.GetPhysicalProcessId( pProcessId ) == Constants.S_OK )
            {
                if ( pProcessId[ 0 ].ProcessIdType == (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM )
                {
                    int processId = (int)pProcessId[ 0 ].dwProcessId;

                    try
                    {
                        Process physicalProcess = Process.GetProcessById( processId );
                        physicalProcess.Kill();
                    }
                    catch ( ArgumentException )
                    {
                        // Process has already been closed, for whatever reason
                    }
                }
            }

            return Constants.S_OK;
        }

        #endregion

        #region Deprecated interface methods
        // These methods are not called by the Visual Studio debugger, so they don't need to be implemented

        int IDebugEngine2.EnumPrograms( out IEnumDebugPrograms2 programs )
        {
            Debug.Fail( "This function is not called by the debugger" );

            programs = null;
            return Constants.E_NOTIMPL;
        }

        public int Execute()
        {
            Debug.Fail( "This function is not called by the debugger." );
            return Constants.E_NOTIMPL;
        }

        #endregion

    }
}
